#ifndef __U2GLESFBORTT_H__
#define __U2GLESFBORTT_H__


#include "U2GLESPreRequest.h"
#include "U2GLESRenderTexture.h"
#include "U2GLESContext.h"
#include "U2GLESFrameBufferObject.h"


U2EG_NAMESPACE_BEGIN


class GLESFBOManager;
class GLESRenderBuffer;


/** RenderTexture for GL ES FBO
*/
class _U2GLESShare GLESFBORenderTexture: public U2GLESRenderTexture
{
public:
    GLESFBORenderTexture(GLESFBOManager *manager, const U2String &name, const U2GLESSurfaceDesc &target, bool writeGamma, u2uint fsaa);

    virtual void getCustomAttribute(const U2String& name, void* pData);

	/// Override needed to deal with multisample buffers
	virtual void swapBuffers(bool waitForVSync = true);

	/// Override so we can attach the depth buffer to the FBO
	virtual bool attachDepthBuffer( U2DepthBuffer *depthBuffer );
	virtual void detachDepthBuffer();
	virtual void _detachDepthBuffer();

protected:
    GLESFrameBufferObject mFB;
};

/** Factory for GL Frame Buffer Objects, and related things.
*/
class _U2GLESShare GLESFBOManager: public U2GLESRTTManager
{
public:
    GLESFBOManager();
	~GLESFBOManager();
    
    /** Bind a certain render target if it is a FBO. If it is not a FBO, bind the
        main frame buffer.
    */
    void bind(U2RenderTarget *target);
    
    /** Unbind a certain render target. No-op for FBOs.
    */
    void unbind(U2RenderTarget *target) {};
    
    /** Get best depth and stencil supported for given internalFormat
    */
    void getBestDepthStencil(GLenum internalFormat, GLenum *depthFormat, GLenum *stencilFormat);
    
    /** Create a texture rendertarget object
    */
    virtual GLESFBORenderTexture *createRenderTexture(const U2String &name, 
		const U2GLESSurfaceDesc &target, bool writeGamma, u2uint fsaa);

	/** Create a multi render target 
	*/
    /**********************@@@@@@@@@@@@@@@@@@@@@
	virtual MultiRenderTarget* createMultiRenderTarget(const U2String & name);
    */
    
    /** Request a render buffer. If format is GL_NONE, return a zero buffer.
    */
    U2GLESSurfaceDesc requestRenderBuffer(GLenum format, size_t width, size_t height, u2uint fsaa);
    /** Request the specify render buffer in case shared somewhere. Ignore
        silently if surface.buffer is 0.
    */
    void requestRenderBuffer(const U2GLESSurfaceDesc &surface);
    /** Release a render buffer. Ignore silently if surface.buffer is 0.
    */
    void releaseRenderBuffer(const U2GLESSurfaceDesc &surface);
    
    /** Check if a certain format is usable as FBO rendertarget format
    */
    bool checkFormat(PixelFormat format) { return mProps[format].valid; }
    
    /** Get a FBO without depth/stencil for temporary use, like blitting between textures.
    */
    GLuint getTemporaryFBO() { return mTempFBO; }

private:

    /** Frame Buffer Object properties for a certain texture format.
    */
    struct FormatProperties
    {
        bool valid; // This format can be used as RTT (FBO)
        
        /** Allowed modes/properties for this pixel format
        */
        struct Mode
        {
            size_t depth;     // Depth format (0=no depth)
            size_t stencil;   // Stencil format (0=no stencil)
        };
        
        std::vector<Mode> modes;
    };

    /** Properties for all internal formats defined by OGRE
    */
    FormatProperties mProps[PF_COUNT];
    
    /** Stencil and depth renderbuffers of the same format are re-used between surfaces of the 
        same size and format. This can save a lot of memory when a large amount of rendertargets
        are used.
    */
    struct RBFormat
    {
        RBFormat(GLenum format, size_t width, size_t height, u2uint fsaa):
            format(format), width(width), height(height), samples(fsaa)
        {}
        GLenum format;
        size_t width;
        size_t height;
		u2uint samples;
        // Overloaded comparison operator for usage in map
        bool operator < (const RBFormat &other) const
        {
            if(format < other.format)
            {
                return true;
            }
            else if(format == other.format)
            {
                if(width < other.width)
                {
                    return true;
                }
                else if(width == other.width)
                {
                    if(height < other.height)
                        return true;
					else if (height == other.height)
					{
						if (samples < other.samples)
							return true;
					}
                }
            }
            return false;
        }
    };

    struct RBRef
    {
        RBRef(){}
        RBRef(GLESRenderBuffer *buffer):
            buffer(buffer), refcount(1)
        { }
        GLESRenderBuffer *buffer;
        size_t refcount;
    };

    typedef std::map<RBFormat, RBRef> RenderBufferMap;
    RenderBufferMap mRenderBufferMap;
    // map(format, sizex, sizey) -> [GLSurface*,refcount]
    
    /** Temporary FBO identifier
     */
    GLuint mTempFBO;
    
    /** Detect allowed FBO formats */
    void detectFBOFormats();
    GLuint _tryFormat(GLenum depthFormat, GLenum stencilFormat);
    bool _tryPackedFormat(GLenum packedFormat);
};


U2EG_NAMESPACE_END


#endif
